package br.iars.web.bean;

import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.primefaces.event.FlowEvent;

import br.iars.service.v01.recommendationservice.RecommendationService;
import br.iars.service.v01.recommendationservice.RecommendationService_Service;
import br.iars.service.v01.recommendationservice.exception.RecommendationServiceException;
import br.iars.service.v01.recommendationservice.type.Context;
import br.iars.service.v01.recommendationservice.type.ElementList;
import br.iars.service.v01.recommendationservice.type.PreferenceItem;
import br.iars.service.v01.recommendationservice.type.RecommendRequest;
import br.iars.service.v01.recommendationservice.type.RecommendResponse;
import br.iars.service.v01.recommendationservice.type.RecommendedItem;
import br.iars.service.v01.recommendationservice.type.SetPreferencesRequest;
import br.iars.web.bean.util.NeighborsDTO;
import br.iars.web.exception.SystemException;
import br.iars.web.model.api.ContextModel;
import br.iars.web.model.api.ItemModel;
import br.iars.web.model.api.UserModel;
import br.iars.web.model.entity.Item;
import br.iars.web.model.entity.Preference;
import br.iars.web.model.entity.PreferenceType;
import br.iars.web.model.entity.User;
import br.iars.web.model.impl.ContextModelImpl;
import br.iars.web.model.impl.ItemModelImpl;
import br.iars.web.model.impl.UserModelImpl;
import br.iars.web.util.Constants;
import br.iars.web.util.MessageResource;

public class RecommenderBean extends GenericBean {

	private static final long serialVersionUID = -594375081069647862L;

	private transient Logger logger;

	private UserModel userModel;
	private ContextModel contextModel;
	private ItemModel itemModel;
	private RecommendationService_Service service;
	private RecommendationService recommendationService;

	private String userName;
	private String userEmail;
	private List<Preference> moviesPreferences;
	private List<Preference> singersPreferences;
	private List<Preference> booksPreferences;
	
	private List<Preference> moviesPreferences1;
	private List<Preference> moviesPreferences2;
	private List<Preference> moviesPreferences3;
	private List<Preference> singersPreferences1;
	private List<Preference> singersPreferences2;
	private List<Preference> singersPreferences3;	
	private List<Preference> booksPreferences1;
	private List<Preference> booksPreferences2;
	private List<Preference> booksPreferences3;

	private static final int PREFERENCES_NUMBER = 10;

	private static final int RECOMMENDATION_NUMBER = 10;

	private br.iars.web.model.entity.Context moviesContext;

	private br.iars.web.model.entity.Context singersContext;

	private br.iars.web.model.entity.Context booksContext;

	private List<Preference> moviesOnlyOwnContext;

	private List<Preference> moviesWithAllContext;

	private List<Preference> singersOnlyOwnContext;

	private List<Preference> singersWithAllContext;

	private List<Preference> booksOnlyOwnContext;

	private List<Preference> booksWithAllContext;

	private List<User> users;

	private User selectedUser;

	private List<NeighborsDTO> neighbors;

	public RecommenderBean() throws SystemException {
		super();
		this.logger = Logger.getLogger(getClass());
		this.userModel = new UserModelImpl();
		this.contextModel = new ContextModelImpl();
		this.itemModel = new ItemModelImpl();

		try {
			service = new RecommendationService_Service(new URL("http://localhost:8080/iars-service-0.0.1-SNAPSHOT/RecommendationServiceImpl?wsdl"));
			recommendationService = service.getRecommendationServicePort();
		} catch (Exception e) {
			logger.error("Erro ao instanciar o client do serviço de recomendação", e);
			throw new SystemException(e);
		}

		init();
	}

	public void init() {
		if (moviesContext == null || singersContext == null || booksContext == null || moviesPreferences == null || singersPreferences == null
				|| booksPreferences == null) {
			Preference preference = null;

			moviesContext = contextModel.findByID(Constants.CONTEXT_MOVIES_ID);
			List<Item> movies = moviesContext.getItens();

			singersContext = contextModel.findByID(Constants.CONTEXT_SINGERS_ID);
			List<Item> singers = singersContext.getItens();

			booksContext = contextModel.findByID(Constants.CONTEXT_BOOKS_ID);
			List<Item> books = booksContext.getItens();

			moviesPreferences = new LinkedList<Preference>();
			singersPreferences = new LinkedList<Preference>();
			booksPreferences = new LinkedList<Preference>();

			for (Item movie : movies) {
				preference = new Preference(movie, moviesContext);
				moviesPreferences.add(preference);
			}

			for (Item singer : singers) {
				preference = new Preference(singer, singersContext);
				singersPreferences.add(preference);
			}

			for (Item book : books) {
				preference = new Preference(book, booksContext);
				booksPreferences.add(preference);
			}

			users = userModel.findAll();
			
			moviesPreferences1 = new LinkedList<Preference>();
			moviesPreferences2 = new LinkedList<Preference>();
			moviesPreferences3 = new LinkedList<Preference>();
			
			for (int i = 0; i < moviesPreferences.size(); i++){
				if (i < 10){
					moviesPreferences1.add(moviesPreferences.get(i));
				}else if(i < 20){
					moviesPreferences2.add(moviesPreferences.get(i));
				}else{
					moviesPreferences3.add(moviesPreferences.get(i));
				}
			}
			
			singersPreferences1 = new LinkedList<Preference>();
			singersPreferences2 = new LinkedList<Preference>();
			singersPreferences3 = new LinkedList<Preference>();
			
			for (int i = 0; i < singersPreferences.size(); i++){
				if (i < 10){
					singersPreferences1.add(singersPreferences.get(i));
				}else if(i < 20){
					singersPreferences2.add(singersPreferences.get(i));
				}else{
					singersPreferences3.add(singersPreferences.get(i));
				}
			}
			
			booksPreferences1 = new LinkedList<Preference>();
			booksPreferences2 = new LinkedList<Preference>();
			booksPreferences3 = new LinkedList<Preference>();
			
			for (int i = 0; i < booksPreferences.size(); i++){
				if (i < 10){
					booksPreferences1.add(booksPreferences.get(i));
				}else if(i < 20){
					booksPreferences2.add(booksPreferences.get(i));
				}else{
					booksPreferences3.add(booksPreferences.get(i));
				}
			}
		}
	}

	public void clean() {
		moviesPreferences = null;
		moviesWithAllContext = null;
		moviesOnlyOwnContext = null;

		singersPreferences = null;
		singersWithAllContext = null;
		singersOnlyOwnContext = null;

		booksPreferences = null;
		booksWithAllContext = null;
		booksOnlyOwnContext = null;
	}

	public List<NeighborsDTO> getNeighbors() {
		return neighbors;
	}

	public void setNeighbors(List<NeighborsDTO> neighbors) {
		this.neighbors = neighbors;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public List<Preference> getMoviesOnlyOwnContext() {
		return moviesOnlyOwnContext;
	}

	public void setMoviesOnlyOwnContext(List<Preference> moviesOnlyOwnContext) {
		this.moviesOnlyOwnContext = moviesOnlyOwnContext;
	}

	public List<Preference> getMoviesWithAllContext() {
		return moviesWithAllContext;
	}

	public void setMoviesWithAllContext(List<Preference> moviesWithAllContext) {
		this.moviesWithAllContext = moviesWithAllContext;
	}

	public List<Preference> getSingersOnlyOwnContext() {
		return singersOnlyOwnContext;
	}

	public void setSingersOnlyOwnContext(List<Preference> singersOnlyOwnContext) {
		this.singersOnlyOwnContext = singersOnlyOwnContext;
	}

	public List<Preference> getSingersWithAllContext() {
		return singersWithAllContext;
	}

	public void setSingersWithAllContext(List<Preference> singersWithAllContext) {
		this.singersWithAllContext = singersWithAllContext;
	}

	public List<Preference> getBooksOnlyOwnContext() {
		return booksOnlyOwnContext;
	}

	public void setBooksOnlyOwnContext(List<Preference> booksOnlyOwnContext) {
		this.booksOnlyOwnContext = booksOnlyOwnContext;
	}

	public List<Preference> getBooksWithAllContext() {
		return booksWithAllContext;
	}

	public void setBooksWithAllContext(List<Preference> booksWithAllContext) {
		this.booksWithAllContext = booksWithAllContext;
	}

	public String getUserEmail() {
		return userEmail;
	}

	public void setUserEmail(String userEmail) {
		this.userEmail = userEmail;
	}

	public List<Preference> getMoviesPreferences() {
		return moviesPreferences;
	}

	public void setMoviesPreferences(List<Preference> moviesPreferences) {
		this.moviesPreferences = moviesPreferences;
	}

	public List<Preference> getSingersPreferences() {
		return singersPreferences;
	}

	public void setSingersPreferences(List<Preference> singersPreferences) {
		this.singersPreferences = singersPreferences;
	}

	public List<Preference> getBooksPreferences() {
		return booksPreferences;
	}

	public void setBooksPreferences(List<Preference> booksPreferences) {
		this.booksPreferences = booksPreferences;
	}

	public List<User> getUsers() {
		return users;
	}

	public void setUsers(List<User> users) {
		this.users = users;
	}

	public User getSelectedUser() {
		return selectedUser;
	}

	public void setSelectedUser(User selectedUser) {
		this.selectedUser = selectedUser;
	}	

	public List<Preference> getMoviesPreferences1() {
		return moviesPreferences1;
	}

	public void setMoviesPreferences1(List<Preference> moviesPreferences1) {
		this.moviesPreferences1 = moviesPreferences1;
	}

	public List<Preference> getMoviesPreferences2() {
		return moviesPreferences2;
	}

	public void setMoviesPreferences2(List<Preference> moviesPreferences2) {
		this.moviesPreferences2 = moviesPreferences2;
	}

	public List<Preference> getMoviesPreferences3() {
		return moviesPreferences3;
	}

	public void setMoviesPreferences3(List<Preference> moviesPreferences3) {
		this.moviesPreferences3 = moviesPreferences3;
	}

	public List<Preference> getSingersPreferences1() {
		return singersPreferences1;
	}

	public void setSingersPreferences1(List<Preference> singersPreferences1) {
		this.singersPreferences1 = singersPreferences1;
	}

	public List<Preference> getSingersPreferences2() {
		return singersPreferences2;
	}

	public void setSingersPreferences2(List<Preference> singersPreferences2) {
		this.singersPreferences2 = singersPreferences2;
	}

	public List<Preference> getSingersPreferences3() {
		return singersPreferences3;
	}

	public void setSingersPreferences3(List<Preference> singersPreferences3) {
		this.singersPreferences3 = singersPreferences3;
	}

	public List<Preference> getBooksPreferences1() {
		return booksPreferences1;
	}

	public void setBooksPreferences1(List<Preference> booksPreferences1) {
		this.booksPreferences1 = booksPreferences1;
	}

	public List<Preference> getBooksPreferences2() {
		return booksPreferences2;
	}

	public void setBooksPreferences2(List<Preference> booksPreferences2) {
		this.booksPreferences2 = booksPreferences2;
	}

	public List<Preference> getBooksPreferences3() {
		return booksPreferences3;
	}

	public void setBooksPreferences3(List<Preference> booksPreferences3) {
		this.booksPreferences3 = booksPreferences3;
	}

	public String save() {
		if (this.userName == null || this.userName.trim().equals("")) {
			addMessageError(MESSAGE_FIELD_NAME, "user_validation_invalid_user_name");
		} else {
			userEmail = generateEmailFrom(userName);

			Map<String, Object> messages = new HashMap<String, Object>();

			if (userModel.findByEmail(this.userEmail) != null) {
				messages.put("user_validation_user_name_already_in_use", null);
			}

			ElementList<PreferenceItem> moviesPreferencesRated = loadPreferencesRated(moviesContext, moviesPreferences, messages);
			ElementList<PreferenceItem> booksPreferencesRated = loadPreferencesRated(booksContext, booksPreferences, messages);
			ElementList<PreferenceItem> singersPreferencesRated = loadPreferencesRated(singersContext, singersPreferences, messages);

			validatePreferences("movies", moviesPreferencesRated, messages);
			validatePreferences("singers", singersPreferencesRated, messages);
			validatePreferences("books", booksPreferencesRated, messages);

			if (messages.isEmpty()) {
				SetPreferencesRequest movies = loadSetPreferencesRequest("Movies", "Movies", moviesPreferencesRated);
				SetPreferencesRequest singers = loadSetPreferencesRequest("Singers", "Singers", singersPreferencesRated);
				SetPreferencesRequest books = loadSetPreferencesRequest("Books", "Books", booksPreferencesRated);

				try {
					User user = new User(userEmail);
					userModel.insert(user);

					recommendationService.setPreferences(movies);
					recommendationService.setPreferences(singers);
					recommendationService.setPreferences(books);

					loadRecommendations(user);

					addMessageSuccess(MESSAGE_FIELD_NAME, "preference_save_success");
					return Constants.NAVIGATION_LIST_RECOMMENDATIONS;
				} catch (RecommendationServiceException e) {
					addMessageError(MESSAGE_FIELD_NAME, "recommendation_load_error");
				} catch (Exception e) {
					addMessageError(MESSAGE_FIELD_NAME, "preference_save_error");
				}

			} else {
				addMessagesError(MESSAGE_FIELD_NAME, messages);
			}
		}
		return "";
	}

	private String generateEmailFrom(String userName) {
		return userName.toLowerCase().replace(' ', '.') + "@email.com";
	}

	private void validatePreferences(String context, ElementList<PreferenceItem> preferencesRated, Map<String, Object> messages) {
		int ratesMissed = 0;
		Object[] args = null;

		if (preferencesRated.getItens().size() != PREFERENCES_NUMBER) {
			ratesMissed = PREFERENCES_NUMBER - preferencesRated.getItens().size();

			if (ratesMissed > 0) {
				args = new Object[] { PREFERENCES_NUMBER, ratesMissed };
				messages.put("preference_validation_" + context + "_should_be_rated", args);
			} else {
				args = new Object[] { preferencesRated.getItens().size(), PREFERENCES_NUMBER };
				messages.put("preference_validation_" + context + "_rate_more_than_necessary", args);
			}
		}
	}

	public String loadRecommendations() {
		try {
			loadRecommendations(new User(this.userEmail));
			return Constants.NAVIGATION_LIST_RECOMMENDATIONS;
		} catch (RecommendationServiceException e) {
			addMessageError(MESSAGE_FIELD_NAME, "recommendation_load_error");
		}
		return "";
	}

	private void loadRecommendations(User user) throws RecommendationServiceException {

		List<User> moviesNeighborsOnlyOwnContext = new LinkedList<User>();
		List<User> singersNeighborsOnlyOwnContext = new LinkedList<User>();
		List<User> booksNeighborsOnlyOwnContext = new LinkedList<User>();

		List<User> moviesNeighborsWithAllContext = new LinkedList<User>();
		List<User> singersNeighborsWithAllContext = new LinkedList<User>();
		List<User> booksNeighborsWithAllContext = new LinkedList<User>();

		/*
		 * Movies recommendations
		 */
		moviesWithAllContext = loadRecommendations(user, moviesContext, false, moviesNeighborsWithAllContext);
		moviesOnlyOwnContext = loadRecommendations(user, moviesContext, true, moviesNeighborsOnlyOwnContext);

		/*
		 * Singers recommendations
		 */
		singersWithAllContext = loadRecommendations(user, singersContext, false, singersNeighborsWithAllContext);
		singersOnlyOwnContext = loadRecommendations(user, singersContext, true, singersNeighborsOnlyOwnContext);

		/*
		 * Books recommendations
		 */
		booksWithAllContext = loadRecommendations(user, booksContext, false, booksNeighborsWithAllContext);
		booksOnlyOwnContext = loadRecommendations(user, booksContext, true, booksNeighborsOnlyOwnContext);

		
		NeighborsDTO n1 = new NeighborsDTO(MessageResource.get("label_neighbors_only_own_context"));
		n1.setMoviesNeighbors(moviesNeighborsOnlyOwnContext);
		n1.setSingersNeighbors(singersNeighborsOnlyOwnContext);
		n1.setBooksNeighbors(booksNeighborsOnlyOwnContext);

		NeighborsDTO n2 = new NeighborsDTO(MessageResource.get("label_neighbors_all_contexts"));
		n2.setMoviesNeighbors(moviesNeighborsWithAllContext);
		n2.setSingersNeighbors(singersNeighborsWithAllContext);
		n2.setBooksNeighbors(booksNeighborsWithAllContext);

		neighbors = new LinkedList<NeighborsDTO>();
		neighbors.add(n1);
		neighbors.add(n2);

	}

	@SuppressWarnings("unchecked")
	private List<Preference> loadRecommendations(User user, br.iars.web.model.entity.Context context, boolean isOnlyOwnContext, List<User> neighbors)
			throws RecommendationServiceException {
		Context c = new Context();
		c.setCompany(context.getCompanyName());
		c.setSystem(context.getSystemName());

		RecommendRequest request = new RecommendRequest();
		request.setUserEmail(user.getEmail());
		request.setContext(c);
		request.setOnlyOwnContext(isOnlyOwnContext);
		request.setQuantity(RECOMMENDATION_NUMBER);
		RecommendResponse response = recommendationService.recommend(request);
		ElementList<RecommendedItem> recommendationsTemp = (ElementList<RecommendedItem>) response.getItens();

		Preference p = null;
		List<Preference> recommendations = new LinkedList<Preference>();
		for (RecommendedItem r : recommendationsTemp.getItens()) {
			p = new Preference();
			p.setRate((double) r.getRate());
			p.setContext(context);
			p.setItem(itemModel.findByID(r.getItemId()));

			recommendations.add(p);
		}
		
		List<String> emails = response.getNeighbors().getItens();
		if (emails != null){
			for (String email : emails){
				neighbors.add(new User(email));
			}
		}
		
		return recommendations;
	}

	public String backToUserPreference() {
		init();
		return Constants.NAVIGATION_INSERT_PREFERENCES;
	}

	public String backToUsersList() {
		return Constants.NAVIGATION_LIST_USERS;
	}

	public String backToPrincipal() {
		return Constants.NAVIGATION_MAIN;
	}

	private ElementList<PreferenceItem> loadPreferencesRated(br.iars.web.model.entity.Context context, List<Preference> preferences,
			Map<String, Object> messages) {
		PreferenceItem preferenceItem = null;
		ElementList<PreferenceItem> preferencesRated = new ElementList<PreferenceItem>();
		boolean hasInvalidRate = false;

		for (Preference p : preferences) {
			if (p.getRate() == null || p.getRate() == 0F) {
				continue;
			}

			if (p.getRate() > 0 && context.getMinRate() <= p.getRate() && p.getRate() <= context.getMaxRate()) {
				if (context.getPreferenceType().equals(PreferenceType.INTEGER)) {
					if (p.getRate().intValue() != p.getRate()) {
						hasInvalidRate = true;
						continue;
					}
				}

				preferenceItem = new PreferenceItem();
				preferenceItem.setUserEmail(this.userEmail);
				preferenceItem.setItemId(p.getItem().getId());
				preferenceItem.setRate(p.getRate().floatValue());
				preferencesRated.getItens().add(preferenceItem);
			} else {
				hasInvalidRate = true;
			}
		}

		if (hasInvalidRate) {
			Object[] args = new Object[4];
			args[0] = context.getSystemName();
			args[1] = context.getPreferenceType().toString();
			args[2] = context.getMinRate();
			args[3] = context.getMaxRate();
			messages.put("preference_validation_invalid_rate", args);
		}

		return preferencesRated;
	}

	private SetPreferencesRequest loadSetPreferencesRequest(String company, String system, ElementList<PreferenceItem> preferencesRated) {
		Context context = new Context();
		context.setCompany(company);
		context.setSystem(system);

		SetPreferencesRequest request = new SetPreferencesRequest();
		request.setContext(context);
		request.setPreferences(preferencesRated);

		return request;
	}

	public String showPreferenceForm() {
		init();
		return Constants.NAVIGATION_INSERT_PREFERENCES;
	}

	public String showUserRecommendations() {
		try {
			loadRecommendations(this.selectedUser);

			return Constants.NAVIGATION_LIST_RECOMMENDATIONS;
		} catch (RecommendationServiceException e) {
			addMessageError(MESSAGE_FIELD_NAME, "recommendation_load_error");
		}

		return "";
	}

	public String showUserPreferences() {
		return "";
	}

	public String onFlowProcess(FlowEvent event) {
		System.out.println("Current wizard step:" + event.getOldStep());
		System.out.println("Next step:" + event.getNewStep());

		return event.getNewStep();
	}

}
